{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# TensorFlow初步\n",
    "本节的主要内容截取自Tensorflow官网的Getting Started With Tensorflow教程。读者可以使用该网址查看原始的英文教程：https://www.tensorflow.org/get_started/get_started (需翻墙)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "本教程用于指导Tensorflow的入门编程。再开启练习前，请先确定自己已经安装了Tensorflow。作为本教程的前置条件，你应该熟悉以内容：\n",
    "* 如何使用Python编程\n",
    "* 至少了解少量的数组知识\n",
    "* 理想情况下应该知道机器学习。当然，如果你对机器学习了解较少，甚至毫不知情，你仍然可以使用该教材。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tensorflow提供了多种API接口。其中处于底层的API是Tensorflow Core API，该接口提供了完整的编程控制，对于机器学习研究者以及需要精确控制自己模型的研发人员而言，你可以使用此API进行科学研究。高层的API构建在Tensorflow核心接口之上，其相对核心接口而言更简单，也更容易学习使用。高层API使可重复性任务更容易实现，并且对于不同的用户也有更多的一致性。比如tf.contrib.learn可以帮助你管理：数据集，学习器，训练和执行等内容。但需要注意的是，一些高层API模块仍然在开发中，其命名有可能会被更迭。本教程将从Tensorflow核心API开始讲起，之后我们会使用tf.contrib.learn实现相同的模型。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tensors\n",
    "tensor(张量)是Tensorflow的核心数据单元。一个tensor可以简单的理解为任意维的数组，其中tensor的秩(rank)表示其维度数量。tensor在0维时表示：标量，也就是一个实数；1维时表示：向量；二维时表示：矩阵；而3维以上就表示：张量。如下代码所示："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "3 # 秩为0的tensor；其表示形状为[ ]的标量。\n",
    "[1. ,2., 3.] # 秩为1的tensor; 其表示形状为[3]的向量。\n",
    "[[1., 2., 3.], [4., 5., 6.]] #秩为2的tensor；其表示形状为[2,3]的矩阵。 \n",
    "[[[1., 2., 3.]], [[7., 8., 9.]]] # 秩为3的tensor；其表示形状为[2,1,3]的张量。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## TensorFlow核心API教程\n",
    "### 导入 TensorFlow\n",
    "在Tensorflow规范的编程习惯中，使用以下语句导入Tensorflow库，因此通常会用tf来代替Tensorflow的表示："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import tensorflow as tf"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 计算图\n",
    "你可以认为Tensorflow核心程序由两块单独的部分构成：\n",
    "\n",
    "1.\t构建计算图。\n",
    "2.\t运行计算图。\n",
    "\n",
    "一个计算图是一系列排列好的Tensorflow图节点操作。每个节点使用0或多个tensor作为输入，并且生成一个tensor作为输出。在Tensorflow中，常数是一种特定的节点，其不需要输入，然后其输出是本身内部储存的值。接下来我们从构建一个简单的计算图开始，如下所示，我们创建两个浮点数作为node1和node2："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "node1 = tf.constant(3.0, tf.float32)\n",
    "node2 = tf.constant(4.0) # 隐式地生成tf.float32\n",
    "print(node1, node2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "请注意，节点打印的结果可能不是你期望的输出值3.0以及4.0。因为这些节点只有在被计算时才会分别生成3.0以及4.0。为了计算这些节点，我们必须通过session(会话)运行计算图。一个session封装了Tensorflow运行时的控制和状态操作。计算图只能通过Session运行，如下代码所示，我们创建了一个`Session`对象，然后调用其run方法去运行计算图node1以及node2。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "sess = tf.Session()\n",
    "print(sess.run([node1, node2]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们还可以通过Tensorflow操作(操作也是节点)来组合节点从而构建更复杂的计算图。如下代码所示，我们可以通过将两个节点使用add操作来生成新的计算图："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "node3 = tf.add(node1, node2)\n",
    "print(\"node3: \", node3)\n",
    "print(\"sess.run(node3): \",sess.run(node3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "就目前而言，由于其只能生成常数结果，这些计算图可能还引发不了你的兴趣。为了输出可变结果，计算图还可以通过占位符(placeholder)进行参数化，从而接收外部输入。如下所示，一个占位符也可以看作是对某类型变量的声明。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "a = tf.placeholder(tf.float32)\n",
    "b = tf.placeholder(tf.float32)\n",
    "adder_node = a + b  # “+”是tf.add(a, b)的简洁表达"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这三行代码有点类似于一个函数，我们首先定义两个输入参数(a和b)，然后使用他们进行运算。如下代码所示，我们可以将tensor作为输入数据，给这些占位符具体的值进行计算："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "print(sess.run(adder_node, {a: 3, b:4.5}))\n",
    "print(sess.run(adder_node, {a: [1,3], b: [2, 4]}))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "同样地，我们还可以在该计算图中添加额外的操作，生成更复杂的计算图。例如，我们在上述的加法操作中再添加乘法操作："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "add_and_triple = adder_node * 3.\n",
    "print(sess.run(add_and_triple, {a: 3, b:4.5}))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "就如前面的内容所示，在机器学习中我们通常想要一个学习模型能够任意的获取输入数据，然后训练模型。为了满足此需求，我们就需要一个可更改的计算图，使其在相同的输入时拥有新的输出结果。而Tensorflow中的Variavles(变量)就允许我们在计算图中添加可训练参数。如下代码所示，便是Tensorflow中变量的构造方式："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "W = tf.Variable([.3], tf.float32)\n",
    "b = tf.Variable([-.3], tf.float32)\n",
    "x = tf.placeholder(tf.float32)\n",
    "linear_model = W * x + b"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在Tensorflow中，常数是通过调用tf.constant函数进行初始化的，一旦初始化后他们的值就不会改变。但是变量在调用tf.Variable函数后并没有被初始化。想要在Tensorflow程序中初始化所有变量，如下所示，你必须显式地调用一个特殊的函数："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "init = tf.global_variables_initializer()\n",
    "sess.run(init)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "你需要注意的是，init函数用于处理Tensorflow子图中所有全局变量的初始化工作。在我们调用sess.run函数之前，变量都不会被初始化。如下所示，由于`x`是一个占位符，当执行`linear_model`函数时，我们可以同时地使用一系列的`x`值作为输入："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "print(sess.run(linear_model, {x:[1,2,3,4]}))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们已经创建了一个线性模型，但我们并不知道该模型的性能如何。想要使用训练数据训练该模型，我们还需要y占位符作为类标，并且还需要一个损失函数。我们将使用标准的均方误差函数作为该线性回归的损失模型，其仅仅是当前模型与训练数据误差的平方再求和。如下所示，linear_model - y 创建了一个向量，其每一元素对应着预测值与真实值的差值。我们通过调用 tf.square 函数对该差值进行平方运算。然后使用 tf.reduce_sum 函数将所有平方误差的值进行累加，形成一个标量损失值："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "y = tf.placeholder(tf.float32)\n",
    "squared_deltas = tf.square(linear_model - y)\n",
    "loss = tf.reduce_sum(squared_deltas)\n",
    "print(sess.run(loss, {x:[1,2,3,4], y:[0,-1,-2,-3]}))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "一个变量可以在初始化时设定特定的值，也可以通过tf.assign函数进行赋值，例如，W = -1 以及 b = 1是本模型的最佳参数。如下所示，我们可以手动的使用tf.assign函数将w和b的值重新修改为-1和1，并将损失值降到0。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "fixW = tf.assign(W, [-1.])\n",
    "fixb = tf.assign(b, [1.])\n",
    "sess.run([fixW, fixb])\n",
    "print(sess.run(loss, {x:[1,2,3,4], y:[0,-1,-2,-3]}))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在本例子中，我们站在了上帝视角知道了W和b的最佳值，但并没有使用学习的方式自动地去寻找最佳模型。接下来我们将正式地使用一个简单的线性模型演示在Tensorflow中如何进行机器学习。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## tf.train API\n",
    "Tensorflow提供了`optimizers`(优化器)去逐步的最小化损失函数。而最简单的优化器是`gradient descent`(梯度下降)优化器，如果对此方法有些陌生，该原理在本书的第二章可以找到。梯度下降法通过计算损失函数的梯度来修改权重变量的值，但通常手动的计算梯度是繁琐而易错的。幸运的是Tensorflow的一大优势就是具备自动求导功能，该方法被封装进了tf.gradients中。为了简化，Tensorflow中的优化器通常隐式地为你完成了这些内容。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "optimizer = tf.train.GradientDescentOptimizer(0.01)\n",
    "train = optimizer.minimize(loss)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "sess.run(init) #将上述的权重变量重新初始化。\n",
    "for i in range(1000):\n",
    "  sess.run(train, {x:[1,2,3,4], y:[0,-1,-2,-3]})\n",
    "\n",
    "print(sess.run([W, b]))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "就是那么简单，你只需要几行代码就完成了简单的线性回归任务。当然，对于复杂一些的模型，你还需要定制输送数据到模型中的方法。因此Tensorflow还提供了高层抽象的API用于构造相同的模式，结构，功能等内容。接下来我们将学习如何使用这些高层抽象的API。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 完整的线性回归模型\n",
    "如下所示，是完整的可训练线性回归模型："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import tensorflow as tf\n",
    "\n",
    "# 模型参数。\n",
    "W = tf.Variable([.3], tf.float32)\n",
    "b = tf.Variable([-.3], tf.float32)\n",
    "# 模型的输入与输出。\n",
    "x = tf.placeholder(tf.float32)\n",
    "linear_model = W * x + b\n",
    "y = tf.placeholder(tf.float32)\n",
    "# 损失函数。\n",
    "loss = tf.reduce_sum(tf.square(linear_model - y)) # 平方和累加\n",
    "# 优化器。\n",
    "optimizer = tf.train.GradientDescentOptimizer(0.01)\n",
    "train = optimizer.minimize(loss)\n",
    "# 训练数据。\n",
    "x_train = [1,2,3,4]\n",
    "y_train = [0,-1,-2,-3]\n",
    "# 训练过程。\n",
    "init = tf.global_variables_initializer()\n",
    "sess = tf.Session()\n",
    "sess.run(init) # 初始化变量。\n",
    "for i in range(1000):\n",
    "  sess.run(train, {x:x_train, y:y_train})\n",
    "\n",
    "# 计算训练精度。\n",
    "curr_W, curr_b, curr_loss  = sess.run([W, b, loss], {x:x_train, y:y_train})\n",
    "print(\"W: %s b: %s loss: %s\"%(curr_W, curr_b, curr_loss))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## tf.contrib.learn\n",
    "`tf.contrib.learn` 是高层的Tensorflow库，其将机器学习过程简化为以下几步：\n",
    "* 训练阶段执行\n",
    "* 评估阶段执行\n",
    "* 数据集管理\n",
    "* 数据供给管理\n",
    "\n",
    "### 基本用法\n",
    "接下来，我们将展示如何使用tf.contrib.learn接口简化线性回归程序："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "import tensorflow as tf\n",
    "# NumPy会经常用于对数据进行载入，操作，预处理。\n",
    "import numpy as np\n",
    "\n",
    "\n",
    "# 特征声明。\n",
    "#Tensorflow提供了许多复杂有用的特征类型，在本例中我们只使用一维的实值特征。\n",
    "features = [tf.contrib.layers.real_valued_column(\"x\", dimension=1)]\n",
    "\n",
    "\n",
    "# estimator(估计器)用于训练和评估。Tensorflow已经预定义了许多类型的估计器，例如：\n",
    "# 线性回归，逻辑回归，线性分类器，逻辑分类器以及大量的神经网络分类器和回归器。\n",
    "#下列代码是线性回归估计器的示例。\n",
    "estimator = tf.contrib.learn.LinearRegressor(feature_columns=features)\n",
    "\n",
    "# Tensorflow还提供许多方法用于读取和设置数据集。接下来我们使用numpy_input_fn函数，\n",
    "# 我们必须要告诉函数有多少批次数据进行训练(num_epochs),\n",
    "# 以及每批数据采样多少条(batch_size).\n",
    "x = np.array([1., 2., 3., 4.])\n",
    "y = np.array([0., -1., -2., -3.])\n",
    "input_fn = tf.contrib.learn.io.numpy_input_fn({\"x\":x}, y, batch_size=4,\n",
    "                                              num_epochs=1000)\n",
    "\n",
    "# 我们通过fit方法调用1000次训练步传送训练数据进行训练。\n",
    "estimator.fit(input_fn=input_fn, steps=1000)\n",
    "\n",
    "# 我们使用evaluate方法评估我们训练的模型如何。在真实的例子中，\n",
    "# 我们需要将数据分成验证数据以及测试数据以防止过拟合。\n",
    "estimator.evaluate(input_fn=input_fn)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 自定义模型\n",
    "tf.contrib.learn 并没有将你困在其预定义的模型中。假设我们想要创建Tensorflow还没实现的自定义模型，我们可能仍然要保留tf.contrib.learn中诸如：数据集，数据供给，训练等抽象方法。接下来，我们将展示如何通过低层的Tensorflow接口实现自定义的线性回归器LinearRegressor。\n",
    "\n",
    "想要通过tf.contrib.learn自定义模型，我们需要使用tf.contrib.learn.Estimator。因此tf.contrib.learn.LinearRegressor其实是tf.contrib.learn.Estimator的一个子类。但和Estimator的子类不同，我们只是简单的提供model_fn给Estimator，并告诉tf.contrib.learn如何评估预测，训练次数，损失等方法。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import tensorflow as tf\n",
    "def model(features, labels, mode):\n",
    "  # 构建线性模型\n",
    "  W = tf.get_variable(\"W\", [1], dtype=tf.float64)\n",
    "  b = tf.get_variable(\"b\", [1], dtype=tf.float64)\n",
    "  y = W*features['x'] + b\n",
    "  # 损失子图。\n",
    "  loss = tf.reduce_sum(tf.square(y - labels))\n",
    "  # 训练子图。\n",
    "  global_step = tf.train.get_global_step()\n",
    "  optimizer = tf.train.GradientDescentOptimizer(0.01)\n",
    "  train = tf.group(optimizer.minimize(loss),\n",
    "                   tf.assign_add(global_step, 1))\n",
    "\n",
    "  # ModelFnOps用于连接我们构建的方法子图\n",
    "  return tf.contrib.learn.ModelFnOps(\n",
    "      mode=mode, predictions=y,\n",
    "      loss=loss,\n",
    "      train_op=train)\n",
    "\n",
    "estimator = tf.contrib.learn.Estimator(model_fn=model)\n",
    "# 定义数据集\n",
    "x = np.array([1., 2., 3., 4.])\n",
    "y = np.array([0., -1., -2., -3.])\n",
    "input_fn = tf.contrib.learn.io.numpy_input_fn({\"x\": x}, y, 4, num_epochs=1000)\n",
    "\n",
    "# 训练模型\n",
    "estimator.fit(input_fn=input_fn, steps=1000)\n",
    "# 评估模型\n",
    "print(estimator.evaluate(input_fn=input_fn, steps=10))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python [conda root]",
   "language": "python",
   "name": "conda-root-py"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
